package com.aigestudio.wheelpicker;

import static com.aigestudio.wheelpicker.util.Constants.DEBUG_COLOR_1;
import static com.aigestudio.wheelpicker.util.Constants.DEBUG_COLOR_2;
import static com.aigestudio.wheelpicker.util.Constants.DEBUG_COLOR_3;
import static com.aigestudio.wheelpicker.util.Constants.DEFAULT_CURTAIN_COLOR;
import static com.aigestudio.wheelpicker.util.Constants.DEFAULT_INDICATOR_COLOR;
import static com.aigestudio.wheelpicker.util.Constants.DEFAULT_TEXT_COLOR;
import static com.aigestudio.wheelpicker.util.Constants.FLOAT_100;
import static com.aigestudio.wheelpicker.util.Constants.INT_0;
import static com.aigestudio.wheelpicker.util.Constants.INT_1000;
import static com.aigestudio.wheelpicker.util.Constants.INT_150;
import static com.aigestudio.wheelpicker.util.Constants.INT_16;
import static com.aigestudio.wheelpicker.util.Constants.INT_18000;
import static com.aigestudio.wheelpicker.util.Constants.INT_2;
import static com.aigestudio.wheelpicker.util.Constants.INT_24;
import static com.aigestudio.wheelpicker.util.Constants.INT_255;
import static com.aigestudio.wheelpicker.util.Constants.INT_7;
import static com.aigestudio.wheelpicker.util.Constants.INT_90;
import static com.aigestudio.wheelpicker.util.Constants.INT_N_1;
import static com.aigestudio.wheelpicker.util.Constants.INT_N_90;

import com.aigestudio.wheelpicker.util.LogUtil;
import com.aigestudio.wheelpicker.util.ResUtil;
import com.aigestudio.wheelpicker.util.TextUtils;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ScrollHelper;
import ohos.agp.components.VelocityDetector;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * 滚轮选择器
 * <p>
 * WheelPicker
 *
 * @author AigeStudio 2015-12-12
 * @author AigeStudio 2016-06-17
 * 更新项目结构
 * <p>
 * New project structure
 * @version 1.1.0
 */
public class WheelPicker extends Component
    implements IDebug, IWheelPicker, Runnable, Component.DrawTask, Component.TouchEventListener {
    /**
     * 滚动状态标识值
     *
     * @see OnWheelChangeListener#onWheelScrollStateChanged(int)
     */
    public static final int SCROLL_STATE_IDLE = 0;

    /**
     * Scroll state Dragging
     */
    public static final int SCROLL_STATE_DRAGGING = 1;

    /**
     * Scroll state Scrolling
     */
    public static final int SCROLL_STATE_SCROLLING = 2;

    /**
     * 数据项对齐方式标识值
     *
     * @see #setItemAlign(int)
     */
    public static final int ALIGN_CENTER = 0;

    /**
     * 数据项对齐方式标识值
     */
    public static final int ALIGN_LEFT = 1;

    /**
     * 数据项对齐方式标识值
     */
    public static final int ALIGN_RIGHT = 2;

    private static final String TAG = WheelPicker.class.getSimpleName();

    private final EventHandler mHandler = new EventHandler(EventRunner.create());

    private Paint mPaint;

    private ScrollHelper mScroller;

    private VelocityDetector mTracker;

    /**
     * Determines whether the current scrolling animation is triggered by touchEvent or setSelectedItemPosition.
     * User added eventListeners will only be fired after touchEvents.
     */
    private boolean isTouchTriggered;

    /**
     * 相关监听器
     *
     * @see OnItemSelectedListener
     */
    private OnItemSelectedListener mOnItemSelectedListener;

    /**
     * 相关监听器
     *
     * @see OnWheelChangeListener
     */
    private OnWheelChangeListener mOnWheelChangeListener;

    private Rect mRectDrawn;

    private Rect mRectIndicatorHead;

    private Rect mRectIndicatorFoot;

    private Rect mRectCurrentItem;

    private Matrix mMatrixRotate;

    private Matrix mMatrixDepth;

    /**
     * 数据源
     */
    private List mListItems;

    /**
     * 最宽的文本
     *
     * @see #setMaximumWidthText(String)
     */
    private String mMaxWidthText;

    private String mSeparator = " : ";

    /**
     * 滚轮选择器中可见的数据项数量和滚轮选择器将会绘制的数据项数量
     *
     * @see #setVisibleItemCount(int)
     */
    private int mVisibleItemCount;

    private int mDrawnItemCount;

    /**
     * 滚轮选择器将会绘制的Item数量的一半
     */
    private int mHalfDrawnItemCount;

    /**
     * 单个文本最大宽高
     */
    private int mTextMaxWidth;

    private int mTextMaxHeight;

    /**
     * 数据项文本颜色以及被选中的数据项文本颜色
     *
     * @see #setItemTextColor(int)
     */
    private int mItemTextColor;

    /**
     * 数据项文本颜色以及被选中的数据项文本颜色
     *
     * @see #setSelectedItemTextColor(int)
     */
    private int mSelectedItemTextColor;

    /**
     * 数据项文本尺寸
     *
     * @see #setItemTextSize(int)
     */
    private int mItemTextSize;

    /**
     * 指示器尺寸
     *
     * @see #setIndicatorSize(int)
     */
    private int mIndicatorSize;

    /**
     * 指示器颜色
     *
     * @see #setIndicatorColor(int)
     */
    private int mIndicatorColor;

    /**
     * 幕布颜色
     *
     * @see #setCurtainColor(int)
     */
    private int mCurtainColor;

    /**
     * 数据项之间间距
     *
     * @see #setItemSpace(int)
     */
    private int mItemSpace;

    /**
     * 数据项对齐方式
     *
     * @see #setItemAlign(int)
     */
    private int mItemAlign;

    /**
     * 滚轮选择器单个数据项高度以及单个数据项一半的高度
     */
    private int mItemHeight;

    private int mHalfItemHeight;

    /**
     * 滚轮选择器内容区域高度的一半
     */
    private int mHalfWheelHeight;

    /**
     * 当前被选中的数据项所显示的数据在数据源中的位置
     *
     * @see #setSelectedItemPosition(int)
     */
    private int mSelectedItemPosition;

    /**
     * 当前被选中的数据项所显示的数据在数据源中的位置
     *
     * @see #getCurrentItemPosition()
     */
    private int mCurrentItemPosition;

    /**
     * 滚轮滑动时可以滑动到的最小/最大的Y坐标
     */
    private int mMinFlingY;

    private int mMaxFlingY;

    /**
     * 滚轮滑动时的最小/最大速度
     */
    private int mMinimumVelocity;

    private int mMaximumVelocity;

    /**
     * 滚轮选择器中心坐标
     */
    private int mWheelCenterX;

    private int mWheelCenterY;

    /**
     * 滚轮选择器绘制中心坐标
     */
    private int mDrawnCenterX;

    private int mDrawnCenterY;

    /**
     * 滚轮选择器视图区域在Y轴方向上的偏移值
     */
    private int mScrollOffsetY;

    /**
     * 滚轮选择器中最宽或最高的文本在数据源中的位置
     */
    private int mTextMaxWidthPosition;

    /**
     * 用户手指上一次触摸事件发生时事件Y坐标
     */
    private int mLastPointY;

    /**
     * 手指触摸屏幕时事件点的Y坐标
     */
    private int mDownPointY;

    /**
     * 点击与触摸的切换阀值
     */
    private int mTouchSlop;

    /**
     * 滚轮选择器的每一个数据项文本是否拥有相同的宽度
     *
     * @see #setSameWidth(boolean)
     */
    private boolean isSameWidth;

    /**
     * 是否显示指示器
     *
     * @see #setIndicator(boolean)
     */
    private boolean isShowIndicator;

    /**
     * 是否显示幕布
     *
     * @see #setCurtain(boolean)
     */
    private boolean isShowCurtain;

    /**
     * 是否显示空气感效果
     *
     * @see #setAtmospheric(boolean)
     */
    private boolean isAtmospheric;

    /**
     * 数据是否循环展示
     *
     * @see #setCyclic(boolean)
     */
    private boolean isCyclic;

    /**
     * 滚轮是否为卷曲效果
     *
     * @see #setCurved(boolean)
     */
    private boolean isCurved;

    /**
     * 是否为点击模式
     */
    private boolean isClick;

    /**
     * 是否为强制结束滑动
     */
    private boolean isForceFinishScroll;

    /**
     * Font typeface path from assets
     */
    private String fontPath;

    private boolean isDebug;

    private Context mContext;

    public WheelPicker(Context context) {
        this(context, null);
    }

    public WheelPicker(Context context, AttrSet attrSet) {
        super(context, attrSet);
        mContext = context;
        init(attrSet);

        // 可见数据项改变后更新与之相关的参数
        // Update relevant parameters when the count of visible item changed
        updateVisibleItemCount();

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setTextSize(mItemTextSize);

        if (!TextUtils.isEmpty(fontPath)) {
            Font typeface = ResUtil.createFont(context, fontPath);
            mPaint.setFont(typeface);
        }

        // 更新文本对齐方式
        // Update alignment of text
        updateItemTextAlign();

        // 计算文本尺寸
        // Correct sizes of text
        computeTextSize();

        mScroller = new ScrollHelper();

        mMinimumVelocity = INT_150;
        mMaximumVelocity = INT_18000;
        mTouchSlop = INT_24;
        mRectDrawn = new Rect();

        mRectIndicatorHead = new Rect();
        mRectIndicatorFoot = new Rect();

        mRectCurrentItem = new Rect();

        mMatrixRotate = new Matrix();
        mMatrixDepth = new Matrix();

        new EventHandler(EventRunner.getMainEventRunner()).postTask(() -> addDrawTask(this));
        setTouchEventListener(this);
    }

    private void init(AttrSet attrSet) {
        new WheelPicker.StyledAttributes(attrSet);
    }

    private class StyledAttributes {
        private String wheelSelectedItemPosition = "wheel_item_text_size_position";

        private String wheelItemTextSize = "wheel_item_text_size";

        private String wheelItemTextColor = "wheel_item_text_color";

        private String wheelSelectedItemTextColor = "wheel_selected_item_text_color";

        private String wheelSameWidth = "wheel_same_width";

        private String wheelMaximumWidthText = "wheel_maximum_width_text";

        private String wheelMaximumWidthTextPosition = "wheel_maximum_width_text_position";

        private String wheelVisibleItemCount = "wheel_visible_item_count";

        private String wheelItemSpace = "wheel_item_space";

        private String wheelCyclic = "wheel_cyclic";

        private String wheelIndicator = "wheel_indicator";

        private String wheelIndicatorColor = "wheel_indicator_color";

        private String wheelIndicatorSize = "wheel_indicator_size";

        private String wheelCurtain = "wheel_curtain";

        private String wheelCurtainColor = "wheel_curtain_color";

        private String wheelAtmospheric = "wheel_atmospheric";

        private String wheelCurved = "wheel_curved";

        private String wheelItemAlign = "wheel_item_align";

        private String wheelFontPath = "wheel_font_path";

        StyledAttributes(AttrSet attrSet) {
            mListItems = Arrays.asList(getDefaultStringArray());
            mItemTextSize = (int) ResUtil.getDimenFromAttrSet(attrSet, wheelItemTextSize,
                ResUtil.getDimen(mContext, ResourceTable.Float_WheelItemTextSize));

            mVisibleItemCount = ResUtil.getIntFromAttrSet(attrSet, wheelVisibleItemCount, INT_7);

            mSelectedItemPosition = ResUtil.getIntFromAttrSet(attrSet, wheelSelectedItemPosition, INT_0);

            isSameWidth = ResUtil.getBoolFromAttrSet(attrSet, wheelSameWidth, false);

            mTextMaxWidthPosition = ResUtil.getIntFromAttrSet(attrSet, wheelMaximumWidthTextPosition, INT_N_1);

            mMaxWidthText = ResUtil.getStringFromAttrSet(attrSet, wheelMaximumWidthText, "");

            mSelectedItemTextColor = ResUtil.getColorFromAttrSet(attrSet, wheelSelectedItemTextColor, INT_N_1);

            mItemTextColor = ResUtil.getColorFromAttrSet(attrSet, wheelItemTextColor, DEFAULT_TEXT_COLOR);

            mItemSpace = (int) ResUtil.getDimenFromAttrSet(attrSet, wheelItemSpace,
                ResUtil.getDimen(mContext, ResourceTable.Float_WheelItemSpace));

            isCyclic = ResUtil.getBoolFromAttrSet(attrSet, wheelCyclic, false);

            initIndicator(attrSet);

            initCurtain(attrSet);

            isAtmospheric = ResUtil.getBoolFromAttrSet(attrSet, wheelAtmospheric, false);

            isCurved = ResUtil.getBoolFromAttrSet(attrSet, wheelCurved, false);

            String textAlign = ResUtil.getStringFromAttrSet(attrSet, wheelItemAlign, "center");

            mItemAlign = getItemTextAlign(textAlign.toLowerCase(Locale.ENGLISH));

            initTextFont(attrSet);
        }

        private void initTextFont(AttrSet attrSet) {
            fontPath = ResUtil.getStringFromAttrSet(attrSet, wheelFontPath, "");
        }

        private void initCurtain(AttrSet attrSet) {
            isShowCurtain = ResUtil.getBoolFromAttrSet(attrSet, wheelCurtain, false);

            mCurtainColor = ResUtil.getColorFromAttrSet(attrSet, wheelCurtainColor, DEFAULT_CURTAIN_COLOR);
        }

        private void initIndicator(AttrSet attrSet) {
            isShowIndicator = ResUtil.getBoolFromAttrSet(attrSet, wheelIndicator, false);

            mIndicatorColor = ResUtil.getColorFromAttrSet(attrSet, wheelIndicatorColor, DEFAULT_INDICATOR_COLOR);

            mIndicatorSize = (int) ResUtil.getDimenFromAttrSet(attrSet, wheelIndicatorSize,
                ResUtil.getDimen(mContext, ResourceTable.Float_WheelIndicatorSize));
        }
    }

    private int getItemTextAlign(String align) {
        switch (align) {
            case "left":
                return ALIGN_LEFT;
            case "right":
                return ALIGN_RIGHT;
            default:
                return ALIGN_CENTER;
        }
    }

    private String[] getDefaultStringArray() {
        return new String[] {
            "AigeStudio", "Aige", "爱哥", "الحب  اخي,", "jeg elsker", "사랑해요 형", "Amor de irmão", "armastan", "愛の兄",
            "обичам те", "любовь - братa", "miłość bracie", "Liebe", "Lamour", "rakastan sinua", "láska..",
            "dragostea.", "jag älskar", "ljubezen, brat.", "愛哥", "ชอบพี่", "αγάπη μου", "a szerelem.",
            "Amore, fratello."
        };
    }

    private void updateVisibleItemCount() {
        if (mVisibleItemCount < INT_2) {
            throw new ArithmeticException("Wheel's visible item count can not be less than 2!");
        }

        // 确保滚轮选择器可见数据项数量为奇数
        // Be sure count of visible item is odd number
        if (mVisibleItemCount % INT_2 == 0) {
            mVisibleItemCount += 1;
        }
        mDrawnItemCount = mVisibleItemCount + INT_2;
        mHalfDrawnItemCount = mDrawnItemCount / INT_2;
    }

    private void computeTextSize() {
        mTextMaxWidth = 0;
        mTextMaxHeight = 0;
        if (isSameWidth) {
            mTextMaxWidth = (int) mPaint.measureText(String.valueOf(mListItems.get(0)));
        } else if (isPosInRang(mTextMaxWidthPosition)) {
            mTextMaxWidth = (int) mPaint.measureText(String.valueOf(mListItems.get(mTextMaxWidthPosition)));
        } else if (!TextUtils.isEmpty(mMaxWidthText)) {
            mTextMaxWidth = (int) mPaint.measureText(mMaxWidthText);
        } else {
            for (Object obj : mListItems) {
                String text = String.valueOf(obj);
                int width = (int) mPaint.measureText(text);
                mTextMaxWidth = Math.max(mTextMaxWidth, width);
            }
        }
        Paint.FontMetrics metrics = mPaint.getFontMetrics();
        mTextMaxHeight = (int) (metrics.bottom - metrics.top);
    }

    private void updateItemTextAlign() {
        switch (mItemAlign) {
            case ALIGN_LEFT:
                mPaint.setTextAlign(TextAlignment.LEFT);
                break;
            case ALIGN_RIGHT:
                mPaint.setTextAlign(TextAlignment.RIGHT);
                break;
            default:
                mPaint.setTextAlign(TextAlignment.CENTER);
                break;
        }
    }

    private void onMeasure() {
        // 计算原始内容尺寸
        // Correct sizes of original content
        int resultWidth = mTextMaxWidth;
        int resultHeight = mTextMaxHeight * mVisibleItemCount + mItemSpace * (mVisibleItemCount - 1);

        // 如果开启弯曲效果则需要重新计算弯曲后的尺寸
        // Correct view sizes again if curved is enable
        if (isCurved) {
            resultHeight = (int) (INT_2 * resultHeight / Math.PI);
        }
        if (isDebug) {
            LogUtil.error(TAG, "Wheel's content size is " + resultWidth + mSeparator + resultHeight);
        }

        // 考虑内边距对尺寸的影响
        // Consideration padding influence the view sizes
        resultWidth += getPaddingLeft() + getPaddingRight();
        resultHeight += getPaddingTop() + getPaddingBottom();
        if (isDebug) {
            LogUtil.error(TAG, "Wheel's size is " + resultWidth + mSeparator + resultHeight);
        }

        setComponentSize(resultWidth, resultHeight);

        onSizeChanged();
    }

    private void onSizeChanged() {
        // 设置内容区域
        // Set content region
        mRectDrawn.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
            getHeight() - getPaddingBottom());
        if (isDebug) {
            LogUtil.error(TAG,
                "Wheel's drawn rect size is " + mRectDrawn.getWidth() + mSeparator + mRectDrawn.getHeight()
                    + " and location is " + mRectDrawn.left + mSeparator + mRectDrawn.top);
        }

        // 获取内容区域中心坐标
        // Get the center coordinates of content region
        mWheelCenterX = mRectDrawn.getCenterX();
        mWheelCenterY = mRectDrawn.getCenterY();

        // 计算数据项绘制中心
        // Correct item drawn center
        computeDrawnCenter();

        mHalfWheelHeight = mRectDrawn.getHeight() / INT_2;

        mItemHeight = mRectDrawn.getHeight() / mVisibleItemCount;
        mHalfItemHeight = mItemHeight / INT_2;

        // 初始化滑动最大坐标
        // Initialize fling max Y-coordinates
        computeFlingLimitY();

        // 计算指示器绘制区域
        // Correct region of indicator
        computeIndicatorRect();

        // 计算当前选中的数据项区域
        // Correct region of current select item
        computeCurrentItemRect();
    }

    private void computeDrawnCenter() {
        switch (mItemAlign) {
            case ALIGN_LEFT:
                mDrawnCenterX = mRectDrawn.left;
                break;
            case ALIGN_RIGHT:
                mDrawnCenterX = mRectDrawn.right;
                break;
            default:
                mDrawnCenterX = mWheelCenterX;
                break;
        }
        mDrawnCenterY = (int) (mWheelCenterY - ((mPaint.ascent() + mPaint.descent()) / INT_2));
    }

    private void computeFlingLimitY() {
        int currentItemOffset = mSelectedItemPosition * mItemHeight;
        mMinFlingY = isCyclic ? Integer.MIN_VALUE : -mItemHeight * (mListItems.size() - 1) + currentItemOffset;
        mMaxFlingY = isCyclic ? Integer.MAX_VALUE : currentItemOffset;
    }

    private void computeIndicatorRect() {
        if (!isShowIndicator) {
            return;
        }
        int halfIndicatorSize = mIndicatorSize / INT_2;
        int indicatorHeadCenterY = mWheelCenterY + mHalfItemHeight;
        int indicatorFootCenterY = mWheelCenterY - mHalfItemHeight;
        mRectIndicatorHead.set(mRectDrawn.left, indicatorHeadCenterY - halfIndicatorSize, mRectDrawn.right,
            indicatorHeadCenterY + halfIndicatorSize);
        mRectIndicatorFoot.set(mRectDrawn.left, indicatorFootCenterY - halfIndicatorSize, mRectDrawn.right,
            indicatorFootCenterY + halfIndicatorSize);
    }

    private void computeCurrentItemRect() {
        if (!isShowCurtain && mSelectedItemTextColor == INT_N_1) {
            return;
        }
        mRectCurrentItem.set(mRectDrawn.left, mWheelCenterY - mHalfItemHeight, mRectDrawn.right,
            mWheelCenterY + mHalfItemHeight);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        onMeasure();
        if (mOnWheelChangeListener != null) {
            mOnWheelChangeListener.onWheelScrolled(mScrollOffsetY);
        }
        if (mListItems.size() == 0) {
            return;
        }
        int drawnDataStartPos = -mScrollOffsetY / mItemHeight - mHalfDrawnItemCount;
        for (int drawnDataPos = drawnDataStartPos + mSelectedItemPosition, drawnOffsetPos = -mHalfDrawnItemCount;
            drawnDataPos
                < drawnDataStartPos + mSelectedItemPosition + mDrawnItemCount; drawnDataPos++, drawnOffsetPos++) {
            String data = "";
            if (isCyclic) {
                int actualPos = drawnDataPos % mListItems.size();
                actualPos = actualPos < 0 ? (actualPos + mListItems.size()) : actualPos;
                data = String.valueOf(mListItems.get(actualPos));
            } else {
                if (isPosInRang(drawnDataPos)) {
                    data = String.valueOf(mListItems.get(drawnDataPos));
                }
            }
            mPaint.setColor(new Color(mItemTextColor));
            mPaint.setStyle(Paint.Style.FILL_STYLE);
            int mDrawnItemCenterY = mDrawnCenterY + (drawnOffsetPos * mItemHeight) + mScrollOffsetY % mItemHeight;
            LogUtil.error("test_wheel", "data : " + data);
            int distanceToCenter = 0;
            if (isCurved) {
                distanceToCenter = handleShowCurved(mDrawnItemCenterY, canvas);
            }
            if (isAtmospheric) {
                handleAtmospheric(mDrawnItemCenterY);
            }
            // 根据卷曲与否计算数据项绘制Y方向中心坐标
            // Correct item's drawn centerY base on curved state
            int drawnCenterY = isCurved ? mDrawnCenterY - distanceToCenter : mDrawnItemCenterY;

            // 判断是否需要为当前数据项绘制不同颜色
            // Judges need to draw different color for current item or not
            setWheelItemColor(canvas, data, drawnCenterY);

            if (isDebug) {
                handleOnDrawForDebug(canvas, drawnOffsetPos);
            }
        }
        // 是否需要绘制幕布
        // Need to draw curtain or not
        handleShowCurtain(canvas);
        // 是否需要绘制指示器
        // Need to draw indicator or not
        handleShowIndicator(canvas);

        if (isDebug) {
            mPaint.setColor(new Color(DEBUG_COLOR_3));
            mPaint.setStyle(Paint.Style.FILL_STYLE);
            canvas.drawRect(new RectFloat(0, 0, getPaddingLeft(), getHeight()), mPaint);
            canvas.drawRect(new RectFloat(0, 0, getWidth(), getPaddingTop()), mPaint);
            canvas.drawRect(new RectFloat(getWidth() - getPaddingRight(), 0, getWidth(), getHeight()), mPaint);
            canvas.drawRect(new RectFloat(0, getHeight() - getPaddingBottom(), getWidth(), getHeight()), mPaint);
        }
    }

    private void setWheelItemColor(Canvas canvas, String data, int drawnCenterY) {
        if (mSelectedItemTextColor != INT_N_1) {
            canvas.save();
            if (isCurved) {
                canvas.concat(mMatrixRotate);
            }
            canvas.clipRect(new RectFloat(mRectCurrentItem), Canvas.ClipOp.DIFFERENCE);
            canvas.drawText(mPaint, data, mDrawnCenterX, drawnCenterY);
            canvas.restore();

            mPaint.setColor(new Color(mSelectedItemTextColor));
            canvas.save();
            if (isCurved) {
                canvas.concat(mMatrixRotate);
            }
            canvas.clipRect(new RectFloat(mRectCurrentItem));
            canvas.drawText(mPaint, data, mDrawnCenterX, drawnCenterY);
            canvas.restore();
        } else {
            canvas.save();
            canvas.clipRect(new RectFloat(mRectDrawn));
            if (isCurved) {
                canvas.concat(mMatrixRotate);
            }
            canvas.drawText(mPaint, data, mDrawnCenterX, drawnCenterY);
            canvas.restore();
        }
    }

    private void handleOnDrawForDebug(Canvas canvas, int drawnOffsetPos) {
        canvas.save();
        canvas.clipRect(new RectFloat(mRectDrawn));
        mPaint.setColor(new Color(DEBUG_COLOR_1));
        int lineCenterY = mWheelCenterY + (drawnOffsetPos * mItemHeight);
        Point startPoint = new Point(mRectDrawn.left, lineCenterY);
        Point stopPoint = new Point(mRectDrawn.right, lineCenterY);
        canvas.drawLine(startPoint, stopPoint, mPaint);
        mPaint.setColor(new Color(DEBUG_COLOR_2));
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        int top = lineCenterY - mHalfItemHeight;
        canvas.drawRect(new RectFloat(mRectDrawn.left, top, mRectDrawn.right, top + mItemHeight), mPaint);
        canvas.restore();
    }

    private int handleShowCurved(int drawnItemCenterY, Canvas canvas) {
        // 计算数据项绘制中心距离滚轮中心的距离比率
        // Correct ratio of item's drawn center to wheel center
        float ratio = (mDrawnCenterY - Math.abs(mDrawnCenterY - drawnItemCenterY) - mRectDrawn.top) * 1.0F / (
            mDrawnCenterY - mRectDrawn.top);

        // 计算单位
        // Correct unit
        int unit = 0;
        if (drawnItemCenterY > mDrawnCenterY) {
            unit = 1;
        } else if (drawnItemCenterY < mDrawnCenterY) {
            unit = INT_N_1;
        }

        float degree = -(1 - ratio) * INT_90 * unit;

        LogUtil.error("test_wheel", "degree 1 : " + degree);
        if (degree < INT_N_90) {
            degree = INT_N_90;
        }
        if (degree > INT_90) {
            degree = INT_90;
        }
        LogUtil.error("test_wheel", "degree 2 : " + degree);
        int distanceToCenter = computeSpace((int) degree);

        int transX = mWheelCenterX;
        switch (mItemAlign) {
            case ALIGN_LEFT:
                transX = mRectDrawn.left;
                break;
            case ALIGN_RIGHT:
                transX = mRectDrawn.right;
                break;
            default:
                LogUtil.error(TAG, "Default align center");
                break;
        }
        int transY = mWheelCenterY - distanceToCenter;
        mMatrixRotate.preTranslate(-transX, -transY);
        mMatrixRotate.postTranslate(transX, transY);

        mMatrixDepth.preTranslate(-transX, -transY);
        mMatrixDepth.postTranslate(transX, transY);

        mMatrixRotate.postConcat(mMatrixDepth);

        return distanceToCenter;
    }

    private void handleShowCurtain(Canvas canvas) {
        if (isShowCurtain) {
            mPaint.setColor(new Color(mCurtainColor));
            mPaint.setStyle(Paint.Style.FILL_STYLE);
            canvas.drawRect(new RectFloat(mRectCurrentItem), mPaint);
        }
    }

    private void handleShowIndicator(Canvas canvas) {
        if (isShowIndicator) {
            mPaint.setColor(new Color(mIndicatorColor));
            mPaint.setStyle(Paint.Style.FILL_STYLE);
            canvas.drawRect(new RectFloat(mRectIndicatorHead), mPaint);
            canvas.drawRect(new RectFloat(mRectIndicatorFoot), mPaint);
        }
    }

    private void handleAtmospheric(int drawnItemCenterY) {
        if (isAtmospheric) {
            int alpha = (int) ((mDrawnCenterY - Math.abs(mDrawnCenterY - drawnItemCenterY)) * 1.0F / mDrawnCenterY
                * INT_255);
            alpha = alpha < 0 ? 0 : alpha;
            float alphaFloat = (float) alpha / (float) INT_255;
            float value = Math.round(alphaFloat * FLOAT_100) / FLOAT_100;
            mPaint.setAlpha(value);
        }
    }

    private boolean isPosInRang(int position) {
        return position >= 0 && position < mListItems.size();
    }

    private int computeSpace(int degree) {
        return (int) (Math.sin(Math.toRadians(degree)) * mHalfWheelHeight);
    }

    private void handleActionDown(MmiPoint point, TouchEvent event) {
        isTouchTriggered = true;
        if (mTracker == null) {
            mTracker = VelocityDetector.obtainInstance();
        } else {
            mTracker.clear();
        }
        mTracker.addEvent(event);
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            isForceFinishScroll = true;
        }
        mDownPointY = (int) point.getY();
        mLastPointY = (int) point.getY();
    }

    private void handleActionUp(TouchEvent event) {
        mTracker.addEvent(event);

        mTracker.calculateCurrentVelocity(INT_1000);

        // 根据速度判断是该滚动还是滑动
        // Judges the WheelPicker is scroll or fling base on current velocity
        isForceFinishScroll = false;
        int velocity = (int) mTracker.getVerticalVelocity();
        if (Math.abs(velocity) > mMinimumVelocity) {
            mScroller.doFling(0, mScrollOffsetY, 0, velocity, 0, 0, mMinFlingY, mMaxFlingY);
            mScroller.startScrollY(mScroller.getCurrValue(AXIS_Y) + computeDistanceToEndPoint(
                mScroller.getCurrValue(AXIS_Y) % mItemHeight), 0);
        } else {
            mScroller.startScroll(0, mScrollOffsetY, 0, computeDistanceToEndPoint(mScrollOffsetY % mItemHeight));
        }
        // 校正坐标
        // Correct coordinates
        if (!isCyclic) {
            if (mScroller.getCurrValue(AXIS_Y) > mMaxFlingY) {
                mScroller.startScrollY(mMaxFlingY, 0);
            } else if (mScroller.getCurrValue(ScrollHelper.AXIS_Y) < mMinFlingY) {
                mScroller.startScrollY(mMinFlingY, 0);
            }
        }
        mHandler.postTask(this);
        if (mTracker != null) {
            mTracker.clear();
            mTracker = null;
        }
    }

    private void handleActionCancel() {
        if (mTracker != null) {
            mTracker.clear();
            mTracker = null;
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        final int activePointerIndex = event.getIndex();
        MmiPoint point = event.getPointerScreenPosition(activePointerIndex);
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                handleActionDown(point, event);
                break;
            case TouchEvent.POINT_MOVE:
                if (Math.abs(mDownPointY - point.getY()) < mTouchSlop) {
                    isClick = true;
                    break;
                }
                isClick = false;
                if (mTracker == null) {
                    mTracker = VelocityDetector.obtainInstance();
                }
                mTracker.addEvent(event);
                if (mOnWheelChangeListener != null) {
                    mOnWheelChangeListener.onWheelScrollStateChanged(SCROLL_STATE_DRAGGING);
                }
                // 滚动内容
                // Scroll WheelPicker's content
                float move = point.getY() - mLastPointY;

                if (Math.abs(move) < 1) {
                    break;
                }
                mScrollOffsetY += move;
                mLastPointY = (int) point.getY();
                new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                if (isClick && !isForceFinishScroll) {
                    break;
                }
                handleActionUp(event);
                break;
            case TouchEvent.CANCEL:
                handleActionCancel();
                break;
            default:
                LogUtil.error(TAG, "Unhandled touch event : " + event.getAction());
        }
        return true;
    }

    private int computeDistanceToEndPoint(int remainder) {
        if (Math.abs(remainder) > mHalfItemHeight) {
            if (mScrollOffsetY < 0) {
                return -mItemHeight - remainder;
            } else {
                return mItemHeight - remainder;
            }
        } else {
            return -remainder;
        }
    }

    @Override
    public void run() {
        if (mListItems == null || mListItems.size() == 0) {
            return;
        }
        if (mScroller.isFinished() && !isForceFinishScroll) {
            if (mItemHeight == 0) {
                return;
            }
            int position = (-mScrollOffsetY / mItemHeight + mSelectedItemPosition) % mListItems.size();
            position = position < 0 ? position + mListItems.size() : position;
            if (isDebug) {
                LogUtil.error(TAG, position + ":" + mListItems.get(position) + ":" + mScrollOffsetY);
            }
            mCurrentItemPosition = position;
            if (mOnItemSelectedListener != null && isTouchTriggered) {
                mOnItemSelectedListener.onItemSelected(this, mListItems.get(position), position);
            }
            if (mOnWheelChangeListener != null && isTouchTriggered) {
                mOnWheelChangeListener.onWheelSelected(position);
                mOnWheelChangeListener.onWheelScrollStateChanged(SCROLL_STATE_IDLE);
            }
        }
        if (mScroller.updateScroll()) {
            if (mOnWheelChangeListener != null) {
                mOnWheelChangeListener.onWheelScrollStateChanged(SCROLL_STATE_SCROLLING);
            }
            mScrollOffsetY = mScroller.getCurrValue(AXIS_Y);
            new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
            mHandler.postTask(this, INT_16);
        }
    }

    @Override
    public void setDebug(boolean isDebugVar) {
        isDebug = isDebugVar;
    }

    @Override
    public int getVisibleItemCount() {
        return mVisibleItemCount;
    }

    @Override
    public void setVisibleItemCount(int count) {
        mVisibleItemCount = count;
        updateVisibleItemCount();
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public boolean isCyclic() {
        return isCyclic;
    }

    @Override
    public void setCyclic(boolean isCyclicVar) {
        isCyclic = isCyclicVar;
        computeFlingLimitY();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        mOnItemSelectedListener = listener;
    }

    @Override
    public int getSelectedItemPosition() {
        return mSelectedItemPosition;
    }

    @Override
    public void setSelectedItemPosition(int position) {
        setSelectedItemPosition(position, true);
    }

    /**
     * Sets the selected item position
     *
     * @param position position of the item
     * @param isAnimated to enable animation
     */
    public void setSelectedItemPosition(int position, final boolean isAnimated) {
        isTouchTriggered = false;
        // We go non-animated regardless of "isAnimated" parameter if scroller is in motion
        if (isAnimated && mScroller.isFinished()) {
            int length = getData().size();
            int itemDifference = position - mCurrentItemPosition;
            if (itemDifference == 0) {
                return;
            }
            if (isCyclic && Math.abs(itemDifference) > (length / INT_2)) { // Find the shortest path if it's cyclic
                itemDifference += (itemDifference > 0) ? -length : length;
            }
            mScroller.startScroll(0, mScroller.getCurrValue(AXIS_Y), 0, (-itemDifference) * mItemHeight);
            mHandler.postTask(this);
        } else {
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            int positionTemp = Math.min(position, mListItems.size() - 1);
            int positionTemp2 = Math.max(positionTemp, 0);
            mSelectedItemPosition = positionTemp2;
            mCurrentItemPosition = positionTemp2;
            mScrollOffsetY = 0;
            computeFlingLimitY();
            postLayout();
            new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
        }
    }

    @Override
    public int getCurrentItemPosition() {
        return mCurrentItemPosition;
    }

    @Override
    public List getData() {
        return mListItems;
    }

    @Override
    public void setData(List data) {
        if (data == null) {
            throw new NullPointerException("WheelPicker's data can not be null!");
        }
        mListItems = data;

        // 重置位置
        if (mSelectedItemPosition > data.size() - 1 || mCurrentItemPosition > data.size() - 1) {
            mSelectedItemPosition = data.size() - 1;
            mCurrentItemPosition = data.size() - 1;
        } else {
            mSelectedItemPosition = mCurrentItemPosition;
        }
        mScrollOffsetY = 0;
        computeTextSize();
        computeFlingLimitY();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::postLayout);
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public void setSameWidth(boolean isSameSize) {
        isSameWidth = isSameSize;
        computeTextSize();
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public boolean hasSameWidth() {
        return isSameWidth;
    }

    @Override
    public void setOnWheelChangeListener(OnWheelChangeListener listener) {
        mOnWheelChangeListener = listener;
    }

    @Override
    public String getMaximumWidthText() {
        return mMaxWidthText;
    }

    @Override
    public void setMaximumWidthText(String text) {
        if (text == null) {
            throw new NullPointerException("Maximum width text can not be null!");
        }
        mMaxWidthText = text;
        computeTextSize();
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getMaximumWidthTextPosition() {
        return mTextMaxWidthPosition;
    }

    @Override
    public void setMaximumWidthTextPosition(int position) {
        if (!isPosInRang(position)) {
            throw new ArrayIndexOutOfBoundsException(
                "Maximum width text Position must in [0, " + mListItems.size() + "), but current is " + position);
        }
        mTextMaxWidthPosition = position;
        computeTextSize();
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getSelectedItemTextColor() {
        return mSelectedItemTextColor;
    }

    @Override
    public void setSelectedItemTextColor(int color) {
        mSelectedItemTextColor = color;
        computeCurrentItemRect();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getItemTextColor() {
        return mItemTextColor;
    }

    @Override
    public void setItemTextColor(int color) {
        mItemTextColor = color;
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getItemTextSize() {
        return mItemTextSize;
    }

    @Override
    public void setItemTextSize(int size) {
        mItemTextSize = size;
        mPaint.setTextSize(mItemTextSize);
        computeTextSize();
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getItemSpace() {
        return mItemSpace;
    }

    @Override
    public void setItemSpace(int space) {
        mItemSpace = space;
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public void setIndicator(boolean isShowIndicatorVar) {
        isShowIndicator = isShowIndicatorVar;
        computeIndicatorRect();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public boolean hasIndicator() {
        return isShowIndicator;
    }

    @Override
    public int getIndicatorSize() {
        return mIndicatorSize;
    }

    @Override
    public void setIndicatorSize(int size) {
        mIndicatorSize = size;
        computeIndicatorRect();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getIndicatorColor() {
        return mIndicatorColor;
    }

    @Override
    public void setIndicatorColor(int color) {
        mIndicatorColor = color;
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public void setCurtain(boolean isShowCurtainVar) {
        isShowCurtain = isShowCurtainVar;
        computeCurrentItemRect();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public boolean hasCurtain() {
        return isShowCurtain;
    }

    @Override
    public int getCurtainColor() {
        return mCurtainColor;
    }

    @Override
    public void setCurtainColor(int color) {
        mCurtainColor = color;
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public void setAtmospheric(boolean isAtmosphericVar) {
        isAtmospheric = isAtmosphericVar;
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public boolean hasAtmospheric() {
        return isAtmospheric;
    }

    @Override
    public boolean isCurved() {
        return isCurved;
    }

    @Override
    public void setCurved(boolean isCurvedVar) {
        isCurved = isCurvedVar;
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public int getItemAlign() {
        return mItemAlign;
    }

    @Override
    public void setItemAlign(int align) {
        mItemAlign = align;
        updateItemTextAlign();
        computeDrawnCenter();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    @Override
    public Font getTypeface() {
        if (mPaint != null) {
            return mPaint.getFont();
        }
        return null;
    }

    @Override
    public void setTypeface(Font tf) {
        if (mPaint != null) {
            mPaint.setFont(tf);
        }
        computeTextSize();
        postLayout();
        new EventHandler(EventRunner.getMainEventRunner()).postTask(this::invalidate);
    }

    /**
     * 滚轮选择器Item项被选中时监听接口
     *
     * @author AigeStudio 2016-06-17
     * 新项目结构
     * @version 1.1.0
     */
    public interface OnItemSelectedListener {
        /**
         * 当滚轮选择器数据项被选中时回调该方法
         * 滚动选择器滚动停止后会回调该方法并将当前选中的数据和数据在数据列表中对应的位置返回
         *
         * @param picker 滚轮选择器
         * @param data 当前选中的数据
         * @param position 当前选中的数据在数据列表中的位置
         */
        void onItemSelected(WheelPicker picker, Object data, int position);
    }

    /**
     * 滚轮选择器滚动时监听接口
     *
     * @author AigeStudio 2016-06-17
     * 新项目结构
     * <p>
     * New project structure
     * @since 2016-06-17
     */
    public interface OnWheelChangeListener {
        /**
         * 当滚轮选择器滚动时回调该方法
         * 滚轮选择器滚动时会将当前滚动位置与滚轮初始位置之间的偏移距离返回，该偏移距离有正负之分，正值表示
         * 滚轮正在往上滚动，负值则表示滚轮正在往下滚动
         * <p>
         * Invoke when WheelPicker scroll stopped
         * WheelPicker will return distance offset which between current scroll position and
         * initial position, this offset is positive or negative, positive means WheelPicker is
         * scrolling from bottom to top, negative means WheelPicker is scrolling from top to bottom
         *
         * @param offset 当前滚轮滚动距离上一次滚轮滚动停止后偏移的距离
         * <p>
         * Distance offset which between current scroll position and initial position
         */
        void onWheelScrolled(int offset);

        /**
         * 当滚轮选择器停止后回调该方法
         * 滚轮选择器停止后会回调该方法并将当前选中的数据项在数据列表中的位置返回
         * <p>
         * Invoke when WheelPicker scroll stopped
         * This method will be called when WheelPicker stop and return current selected item data's
         * position in list
         *
         * @param position 当前选中的数据项在数据列表中的位置
         * <p>
         * Current selected item data's position in list
         */
        void onWheelSelected(int position);

        /**
         * 当滚轮选择器滚动状态改变时回调该方法
         * 滚动选择器的状态总是会在静止、拖动和滑动三者之间切换，当状态改变时回调该方法
         * <p>
         * Invoke when WheelPicker's scroll state changed
         * The state of WheelPicker always between idle, dragging, and scrolling, this method will
         * be called when they switch
         *
         * @param state 滚轮选择器滚动状态，其值仅可能为下列之一
         * {@link WheelPicker#SCROLL_STATE_IDLE}
         * 表示滚动选择器处于静止状态
         * {@link WheelPicker#SCROLL_STATE_DRAGGING}
         * 表示滚动选择器处于拖动状态
         * {@link WheelPicker#SCROLL_STATE_SCROLLING}
         * 表示滚动选择器处于滑动状态
         * <p>
         * State of WheelPicker, only one of the following
         * {@link WheelPicker#SCROLL_STATE_IDLE}
         * Express WheelPicker in state of idle
         * {@link WheelPicker#SCROLL_STATE_DRAGGING}
         * Express WheelPicker in state of dragging
         * {@link WheelPicker#SCROLL_STATE_SCROLLING}
         * Express WheelPicker in state of scrolling
         */
        void onWheelScrollStateChanged(int state);
    }
}